//
//  File.swift
//  
//
//  Created by 钟城广 on 2022/6/29.
//

import Foundation
import CoreBluetooth

typealias DeviceSetupCallback = (_ success:Bool,_ errorMessage:String?)->Void
typealias CentralStateChangedCallback = ()->Void

/// 1.  从扫描结果中，提取对应的设备，如果发现匹配外设进行第2步
/// 2.  关联外设，如果设备未连接，进行连接；如果已连接，注册代理
/// 3. 发现服务（0x2600）
/// 4. 发现特征（0x7000, 0x70001）
/// 5. 发现特征（0x7000）的描述（Descriptor）
/// 6. enable 特征（0x7000）的Indicate
class DeviceInitializer: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate{
    
    internal let otaServiceUUID = CBUUID(string: "2600")
    internal let ctrlUUID = CBUUID(string: "7000")
    internal let dataUUID = CBUUID(string: "7001")
    
    var mCentralMgr:CBCentralManager!
    var peripheral:CBPeripheral?
    var _ctrl:CBCharacteristic?
    var _data:CBCharacteristic?
    var callback:DeviceSetupCallback?
    var centralStateCb: CentralStateChangedCallback?
        
    // 初始化延时任务
    private var dispatchWorkItem:DispatchWorkItem? = nil
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        self.centralStateCb?()
    }
    
    public override init() {
        super.init()
        // STEP 1: 为控制中心在后台创建一个并发队列
        let centralQueue: DispatchQueue = DispatchQueue(label: "com.bluexmicro.centralQueueName", attributes: .concurrent)
        mCentralMgr = CBCentralManager(delegate: self, queue: centralQueue)
    }
    
    public func findAndSetupPeripheral(identifier:UUID, callback:DeviceSetupCallback?){
        self.callback = callback
        self.peripheral = nil
        self._ctrl = nil
        self._data = nil
        if mCentralMgr.state != .poweredOn{
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
                self.launch(identifier)
            })
            return
        }else{
            launch(identifier)
        }
    }
    
    
    public func reconnect(cb:DeviceSetupCallback?){
        self.callback = cb
        if mCentralMgr.state != .poweredOn{
            cb?(false,"Bluetooth excpetion")
        }else{
            guard let target = self.peripheral else{
                cb?(false,"Bluetooth excpetion")
                return
            }
            if(target.state == CBPeripheralState.connected){
                cb?(true,nil)
            }else{
                cancelTimeout()
                dispatchWorkItem = DispatchWorkItem{
                    self.failed(errorMessage: "Timeout: Not Found Target Peripheral")
                }
                // 添加延时任务 7秒后超时
                DispatchQueue.main.asyncAfter(deadline: .now() + 7, execute: dispatchWorkItem!)
                mCentralMgr.connect(target, options: nil)
            }
        }
    }
    
    private func launch(_ identifier: UUID){
        if mCentralMgr.state != .poweredOn{
            failed(errorMessage: "Bluetooth not ready，Please wait centralMamager state to poweredOn and retry")
            return
        }
        guard let peripheral = mCentralMgr.retrievePeripherals(withIdentifiers: [identifier]).first else {
            failed(errorMessage: "Could not obtain peripheral instance")
            return
        }
        cancelTimeout()
        dispatchWorkItem = DispatchWorkItem{
            self.failed(errorMessage: "Timeout: Not Found Target Peripheral")
        }
        // 添加延时任务 7秒后超时
        DispatchQueue.main.asyncAfter(deadline: .now() + 7, execute: dispatchWorkItem!)
        mCentralMgr.delegate = self
        peripheral.delegate = self
        self.peripheral = peripheral
        if peripheral.state == .connected {
            centralManager(mCentralMgr, didConnect: peripheral)
        }else{
            mCentralMgr.connect(peripheral, options: nil)
        }
    }
    
    private func findOtaServices(target: CBPeripheral) -> CBService?{
        let services = target.services ?? []
        var otaService:CBService? = nil
        for service in services {
            if service.uuid == otaServiceUUID{
                otaService = service
                break
            }
        }
        return otaService
    }
    
    private func findOtaCharacteristics(target: CBPeripheral, otaService:CBService){
        let characteristics = otaService.characteristics ?? []
        for characteristic in characteristics {
            if(characteristic.uuid == ctrlUUID){
                _ctrl = characteristic
            }else if(characteristic.uuid == dataUUID){
                _data = characteristic
            }
        }
        if(_ctrl == nil || _data == nil){
            target.discoverCharacteristics([ctrlUUID,dataUUID], for: otaService)
        }else{
            let descriptors = _ctrl!.descriptors ?? []
            if(descriptors.isEmpty){
                target.discoverDescriptors(for: _ctrl!)
            }else{
                target.setNotifyValue(true, for: _ctrl!)
            }
        }
    }
    
    private func cancelTimeout(){
        if !(dispatchWorkItem?.isCancelled ?? false) {
            dispatchWorkItem?.cancel()
        }
    }
    
    private func failed(errorMessage:String){
        print(errorMessage)
        // 取消延时任务
        cancelTimeout()
        if(self.peripheral != nil){
            mCentralMgr.cancelPeripheralConnection(self.peripheral!)
        }
        mCentralMgr.delegate = nil
        self.peripheral?.delegate = nil
        self.callback?(false,errorMessage)
    }
    
    private func ready(){
        cancelTimeout()
        mCentralMgr.delegate = nil
        self.peripheral?.delegate = nil
        self.callback?(true,nil)
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print(peripheral)
        let otaService = findOtaServices(target: peripheral)
        if(otaService == nil){
            peripheral.discoverServices([otaServiceUUID])
        }else{
            findOtaCharacteristics(target: peripheral, otaService: otaService!)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print(peripheral)
        print("\(String(describing: error))")
        // reconnect 一直重连直至超时
        mCentralMgr.connect(peripheral, options: nil)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if error != nil {
            failed(errorMessage: String(describing: error))
        } else {
            let otaService = findOtaServices(target: peripheral)
            if(otaService == nil){
                failed(errorMessage: "This peripheral not support BlueX OTA Service")
            }else{
                findOtaCharacteristics(target: peripheral, otaService: otaService!)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if(service.uuid != otaServiceUUID){
            return
        }
        if(error != nil){
            print(String(describing: error))
            failed(errorMessage: String(describing: error))
            return
        }
        for characteristic in service.characteristics ?? [] {
            if(characteristic.uuid == ctrlUUID){
                _ctrl = characteristic
            }else if(characteristic.uuid == dataUUID){
                _data = characteristic
            }
        }
        if(_ctrl == nil || _data == nil){
            failed(errorMessage: "This peripheral not support BlueX OTA characteristics")
        }else{
            let descriptors = _ctrl!.descriptors ?? []
            if(descriptors.isEmpty){
                peripheral.discoverDescriptors(for: _ctrl!)
            }else{
                peripheral.setNotifyValue(true, for: _ctrl!)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
        if(characteristic.uuid != ctrlUUID){
            return
        }
        if(error != nil){
            failed(errorMessage: String(describing: error))
            return
        }
        peripheral.setNotifyValue(true, for: _ctrl!)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if(characteristic.uuid != ctrlUUID){
            return
        }
        if(error != nil){
            failed(errorMessage: String(describing: error))
            return
        }
        if(characteristic.isNotifying){
            print("characteristic.isNotifying")
            print(characteristic)
            ready()
        }else{
            //  wait timeout or success
        }
    }
}
